﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Compression;
using System.IO;
using System.Net;

namespace Minecraft.Data.NBT {
    public class NBTReader {

        private static UTF8Encoding enc = new UTF8Encoding();

        public static Tag LoadNBT(String filename) {
            List<Byte> data = new List<Byte>();

            using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
                GZipStream gzip = new GZipStream(fs, CompressionMode.Decompress, true);
                Byte[] buffer = new Byte[1024];
                Int32 read = 0;
                while ((read = gzip.Read(buffer, 0, buffer.Length)) > 0) {
                    data.AddRange(buffer.Take(read));
                }
            }

            Byte[] bData = data.ToArray();
            data.Clear();
            data = null;

            Tag root = null;
            ReadTag(0, ref bData, out root, TagType.Unknown, true);

            return root;
        }

        private static int ReadTag(Int32 offset, ref Byte[] data, out Tag tag, TagType knownType, Boolean named) {
            tag = new Tag();

            if (knownType == TagType.Unknown) {
                tag.Type = (TagType)data[offset];
                offset += 1;
            }
            else {
                tag.Type = knownType;
            }

            if (tag.Type != TagType.End && named) {
                Int16 nameLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
                offset += 2;

                tag.Name = enc.GetString(data, offset, nameLength);
                offset += nameLength;
            }
            else {
                tag.Name = null;
            }

            switch (tag.Type) {

                case TagType.End:
                    break;

                case TagType.Byte:
                    tag.Payload = data[offset];
                    offset += 1;
                    break;

                case TagType.Short:
                    tag.Payload = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
                    offset += 2;
                    break;

                case TagType.Int:
                    tag.Payload = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
                    offset += 4;
                    break;

                case TagType.Long:
                    tag.Payload = IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, offset));
                    offset += 8;
                    break;

                case TagType.Float:
                    Byte[] bf = data.Skip(offset).Take(4).ToArray();
                    Array.Reverse(bf);
                    tag.Payload = BitConverter.ToSingle(bf, 0);
                    offset += 4;
                    break;

                case TagType.Double:
                    Byte[] bd = data.Skip(offset).Take(8).ToArray();
                    Array.Reverse(bd);
                    tag.Payload = BitConverter.ToDouble(bd, 0);
                    offset += 8;
                    break;

                case TagType.ByteArray:
                    Int32 brLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
                    offset += 4;
                    tag.Payload = data.Skip(offset).Take(brLength).ToArray();
                    offset += brLength;
                    break;

                case TagType.String:
                    Int16 strLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, offset));
                    offset += 2;
                    tag.Payload = enc.GetString(data, offset, strLength);
                    offset += strLength;
                    break;

                case TagType.List:
                    tag.ListType = (TagType)data[offset];
                    offset += 1;
                    Int32 itemCount = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
                    offset += 4;
                    for (int i = 0; i < itemCount; i++) {
                        Tag item = null;
                        offset = ReadTag(offset, ref data, out item, tag.ListType, false);
                        tag.Children.Add(item);
                    }
                    break;

                case TagType.Compound:
                    Tag child = null;
                    offset = ReadTag(offset, ref data, out child, TagType.Unknown, true);
                    while (child.Type != TagType.End) {
                        tag.Children.Add(child);
                        offset = ReadTag(offset, ref data, out child, TagType.Unknown, true);
                    }
                    break;

                default:
                    break;
            }

            return offset;
        }

    }
}
